【das25 上半复现】【白盒 AES】白盒 aes 初识——CTFapp

常规流程,发现声明了 native 层方法 check 和 get,去查看 so 文件。根据安装包的名字我们应该查看 arm 架构的 so 可能会好一些。

image-20250730093719535

打开之后又我们发现 JNI_Onload 之中和其他函数有花指令,阻止 ida 的分析,清除花指令之后,我们可以跟到 check 函数中的加密函数处。

image-20250730094251037

其中 s1 就是密文,经历了白盒 aes 加密和 crc 白化,先把密文进行 crc 解密

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
#include <stdio.h>
#include <stdint.h>
#include <string.h>

// CRC32查找表
static uint32_t crc32_table[256];
static int table_initialized = 0;

// 初始化CRC32查找表 (对应Java_com_example_test_MainActivity_Get函数)
void init_crc32_table()
{
if (!table_initialized)
{
for (int i = 0; i < 256; i++)
{
uint32_t crc = i;
for (int j = 0; j < 8; j++)
{
if (crc & 1)
{
crc = (crc >> 1) ^ 0xEDB88320;
}
else
{
crc >>= 1;
}
}
crc32_table[i] = crc;
}
table_initialized = 1;
}
}

// CRC32计算函数 (对应sub_3750函数)
uint32_t calculate_crc32(const void *data, size_t length)
{
init_crc32_table();

const uint8_t *bytes = (const uint8_t *)data;
uint32_t crc = 0xFFFFFFFF;

for (size_t i = 0; i < length; i++)
{
crc = crc32_table[(crc ^ bytes[i]) & 0xFF] ^ (crc >> 8);
}

return ~crc;
}

// XOR解密函数 (对应sub_37F0函数)
void xor_decrypt(const uint8_t *input, uint8_t *output, size_t length, uint32_t key)
{
uint32_t crc_key = calculate_crc32(&key, 4);

for (size_t i = 0; i < length; i++)
{
output[i] = input[i] ^ ((crc_key >> (8 * (i & 3))) & 0xFF);
}
}

// 解密主函数
void decrypt_data(const uint8_t *encrypted_data, uint8_t *decrypted_data, size_t length)
{
// 使用题目中的密钥 0x6C6F7665 ("love"的ASCII)
uint32_t key = 0x6C6F7665;
xor_decrypt(encrypted_data, decrypted_data, length, key);
}

// 测试函数
int main()
{
// 从题目中提取的加密数据
uint8_t encrypted_data1[] = {
0x0B, 0xCF, 0x79, 0x7E, 0xC7, 0xEA, 0x5F, 0x31,
0xBC, 0xEE, 0xE7, 0x0B, 0x1E, 0x91, 0xAA, 0xDD};

uint8_t encrypted_data2[] = {
0x6F, 0x07, 0xC1, 0x10, 0x36, 0x75, 0x20, 0x06,
0x32, 0xE3, 0xD0, 0x66, 0xB8, 0x7D, 0xFC, 0x90};

uint8_t decrypted1[16];
uint8_t decrypted2[16];

// 解密第一部分
decrypt_data(encrypted_data1, decrypted1, 16);

// 解密第二部分
decrypt_data(encrypted_data2, decrypted2, 16);

// 计算用于解密的CRC32密钥
uint32_t key = 0x6C6F7665;
uint32_t crc_key = calculate_crc32(&key, 4);
printf("原始密钥: 0x%08X\n", key);
printf("CRC32密钥: 0x%08X\n\n", crc_key);

printf("解密结果1 (HEX): ");
for (int i = 0; i < 16; i++)
{
printf("%02X", decrypted1[i]);
}
printf("\n");

printf("解密结果2 (HEX): ");
for (int i = 0; i < 16; i++)
{
printf("%02X", decrypted2[i]);
}
printf("\n");

printf("完整解密结果 (HEX): ");
for (int i = 0; i < 16; i++)
{
printf("%02X", decrypted1[i]);
}
for (int i = 0; i < 16; i++)
{
printf("%02X", decrypted2[i]);
}
printf("\n\n");

// 尝试解释为字符串
printf("作为字符串:\n");
printf("部分1: ");
for (int i = 0; i < 16; i++)
{
if (decrypted1[i] >= 32 && decrypted1[i] <= 126)
{
printf("%c", decrypted1[i]);
}
else
{
printf(".");
}
}
printf("\n");

printf("部分2: ");
for (int i = 0; i < 16; i++)
{
if (decrypted2[i] >= 32 && decrypted2[i] <= 126)
{
printf("%c", decrypted2[i]);
}
else
{
printf(".");
}
}
printf("\n");

printf("完整字符串: ");
for (int i = 0; i < 16; i++)
{
if (decrypted1[i] >= 32 && decrypted1[i] <= 126)
{
printf("%c", decrypted1[i]);
}
else
{
printf(".");
}
}
for (int i = 0; i < 16; i++)
{
if (decrypted2[i] >= 32 && decrypted2[i] <= 126)
{
printf("%c", decrypted2[i]);
}
else
{
printf(".");
}
}
printf("\n");

return 0;
}

// 通用解密函数,可以处理任意长度的数据
void general_decrypt(const uint8_t *encrypted, uint8_t *decrypted, size_t length, uint32_t key)
{
uint32_t crc_key = calculate_crc32(&key, 4);

printf("\n=== 详细解密过程 ===\n");
printf("原始密钥: 0x%08X\n", key);
printf("CRC32密钥: 0x%08X\n", crc_key);
printf("密钥字节: [0x%02X, 0x%02X, 0x%02X, 0x%02X]\n\n",
(crc_key >> 0) & 0xFF, (crc_key >> 8) & 0xFF,
(crc_key >> 16) & 0xFF, (crc_key >> 24) & 0xFF);

for (size_t i = 0; i < length; i++)
{
uint8_t key_byte = (crc_key >> (8 * (i & 3))) & 0xFF;
decrypted[i] = encrypted[i] ^ key_byte;
printf("位置%2zu: 0x%02X ^ 0x%02X = 0x%02X ('%c')\n",
i, encrypted[i], key_byte, decrypted[i],
(decrypted[i] >= 32 && decrypted[i] <= 126) ? decrypted[i] : '.');
}

printf("\n完整HEX结果: ");
for (size_t i = 0; i < length; i++)
{
printf("%02X", decrypted[i]);
}
printf("\n");
}

得到结果之后白盒 aes 的密文,什么是白盒 aes,请看下面 2 篇文章

https://bbs.kanxue.com/thread-254042.htm

https://bbs.kanxue.com/thread-285052.htm

核心加密与普通的 aes 没有区别,就是 tyibox 的数据比较大,看的让人有点迷糊,题中又使用了 nibble 写法,使得更加难以阅读分析。

image-20250730100629626

整理之后写脚本进行模拟进行 dfa 注入可以得到 fault 数据

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
#include <iostream>
#include <cstdlib>
#include <cmath>
#include <string>
#include <windows.h>
#include <stdio.h>
#include <string.h>
typedef unsigned char u8;
typedef unsigned int u32;

void shiftRows(u8 state[16])
{
u8 tmp = state[1];
state[1] = state[5];
state[5] = state[9];
state[9] = state[13];
state[13] = tmp;
tmp = state[2];
state[2] = state[10];
state[10] = tmp;
tmp = state[6];
state[6] = state[14];
state[14] = tmp;
tmp = state[15];
state[15] = state[11];
state[11] = state[7];
state[7] = state[3];
state[3] = tmp;
}

u32 tyibox[10][16][256];

u8 tbox[16][256];


int index = 0;

void aes_encrypt_by_table(u8 input[16], int isDFA)
{

u32 a, b, c, d;

for (int i = 0; i < 9; i++)
{
if (isDFA && i == 8)
{
input[index] = 0x33;
}
shiftRows(input);

for (int j = 0; j < 4; ++j)
{
a = tyibox[i][4 * j + 0][input[4 * j + 0]];
b = tyibox[i][4 * j + 1][input[4 * j + 1]];
c = tyibox[i][4 * j + 2][input[4 * j + 2]];
d = tyibox[i][4 * j + 3][input[4 * j + 3]];

input[4 * j + 0] = (a >> 24) ^ (b >> 24) ^ (c >> 24) ^ (d >> 24);
input[4 * j + 1] = (a >> 16) ^ (b >> 16) ^ (c >> 16) ^ (d >> 16);
input[4 * j + 2] = (a >> 8) ^ (b >> 8) ^ (c >> 8) ^ (d >> 8);
input[4 * j + 3] = a ^ b ^ c ^ d;
}
}

shiftRows(input);
for (int j = 0; j < 16; j++)
{
input[j] = tbox[j][input[j]];
}
}

int main()
{
unsigned char input1[17] = {0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33};
aes_encrypt_by_table(input1, 0);
for (int i = 0; i < 16; i++)
{
printf("%02x", input1[i]);
}
puts("");
for (int j = 0; j < 16; j++)
{
unsigned char input[17] = {0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33};
aes_encrypt_by_table(input, 1);

for (int i = 0; i < 16; i++)
{
printf("%02x", input[i]);
}
puts("");

index++;
}
}

得到 fault 数据之后可以进行用 phonexAES 进行解密得到最后一轮的轮密钥,

再根据轮密钥使用 aes_keyschedule 反推回原始密钥。

最后把密钥和密文扔进厨子里进行解密。

image-20250730101028822